/*
 * Copyright (C) 2019 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.xuexiang.xui_lib.component.progress;

import com.xuexiang.xui_lib.component.progress.ratingbar.AttrValueUtil;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

/**
 * CircleProgressView
 *
 * @author xuexiang
 * @since 2019-05-12 12:34
 */
public class CircleProgressView extends Component {
    /**
     * animation types supported
     */
    private static final int ACCELERATE_DECELERATE_INTERPOLATOR = 0;
    private static final int LINEAR_INTERPOLATOR = 1;
    private static final int ACCELERATE_INTERPOLATOR = 2;
    private static final int DECELERATE_INTERPOLATOR = 3;
    private static final int OVERSHOOT_INTERPOLATOR = 4;
    private static final int DEFAULT_NUM200 = 200;
    private static final int DEFAULT_NUM20 = 200;
    private static final int DEFAULT_DIVISOR = 2;
    private static final int NUM60 = 60;
    private static final int NUM28 = 28;
    private static final int NUM16 = 16;
    private static final int NUM1200 = 1200;
    private static final int NUM6 = 6;
    private static final int NUM8 = 8;
    private static final int NUM22 = 22;
    private static final int NUM135 = 135;
    private static final int NUM270 = 270;
    private static final int NUM90 = 90;
    private static final int NUM360 = 360;
    private static final int NUM100 = 100;
    private static final float NUM2_7 = 2.7f;
    private static final float NUM3_6 = 3.6f;

    PathEffect pathEffect;
    private DrawTask drawTask;
    /**
     * the type of animation
     */
    //private int mAnimateType = 0;
    /**
     * the progress of start point
     */
    private float mStartProgress = 0;
    /**
     * the progress of end point
     */
    private float mEndProgress = NUM60;
    /**
     * the color of start progress
     */
    private Color mStartColor;
    /**
     * the color of end progress
     */
    private Color mEndColor;
    /**
     * has track of moving or not
     */
    private boolean trackEnabled;
    /**
     * filling the inner space or not
     */
    private boolean fillEnabled;
    /**
     * the stroke width of Track
     */
    private int mTrackWidth;
    /**
     * the stroke width of progress
     */
    private int mProgressWidth;
    /**
     * the size of inner text
     */
    private int mProgressTextSize;
    /**
     * the color of inner text
     */
    private Color mProgressTextColor;
    /**
     * the circle of progress broken or not
     */
    private boolean circleBroken;
    /**
     * the color of progress track
     */
    private Color mTrackColor;
    /**
     * the duration of progress moving
     */
    private int mProgressDuration = NUM1200;
    /**
     * show the inner text or not
     */
    private boolean textVisibility;

    /**
     * the animator of progress moving
     */
    private AnimatorValue progressAnimator;
    /**
     * the progress of moving
     */
    private float moveProgress = 0;
    /**
     * the paint of drawing progress
     */
    private Paint progressPaint;
    /**
     * the paint of drawing track
     */
    private Paint trackPaint;
    /**
     * the gradient of color
     */
    private LinearShader mShader;
    /**
     * the oval's rect shape
     */
    private RectFloat mOval;

    private int curveType;
    private CircleProgressUpdateListener mUpdateListener;

    /**
     * the path of scale zone
     */
    private Path mScaleZonePath;
    /**
     * the width of each scale zone
     */
    private float mScaleZoneWidth;
    /**
     * the length of each scale zone
     */
    private float mScaleZoneLength;
    /**
     * the padding of scale zones
     */
    private int mScaleZonePadding;
    /**
     * open draw the scale zone or not
     */
    private boolean isGraduated = false;
    /**
     * the radius of scale zone corner
     */
    private int mScaleZoneCornerRadius = 0;

  /*  *//**
     * CircleProgressView
     *
     * @param context
     *//*
    public CircleProgressView(Context context) {
        this(context, null);
    }*/

    /**
     * CircleProgressView
     *
     * @param context
     * @param attrs
     */
    public CircleProgressView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * CircleProgressView
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public CircleProgressView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        obtainAttrs(context, attrs, defStyleAttr);
        init();
    }

    private void obtainAttrs(Context context, AttrSet attrs, int defStyleAttr) {
        mStartProgress = AttrValueUtil.get(attrs, "cpv_start_progress", 0);
        mEndProgress = AttrValueUtil.get(attrs, "cpv_end_progress", NUM60);
        if (attrs.getAttr("cpv_start_color").isPresent()) {
            mStartColor = attrs.getAttr("cpv_start_color").get().getColorValue();
        } else {
            mStartColor = new Color(Color.getIntColor("#FFC288"));
        }

        if (attrs.getAttr("cpv_end_color").isPresent()) {
            mEndColor = attrs.getAttr("cpv_end_color").get().getColorValue();
        } else {
            mEndColor = new Color(Color.getIntColor("#FF8F5D"));
        }
        fillEnabled = AttrValueUtil.get(attrs, "cpv_isFilled", false);
        trackEnabled = AttrValueUtil.get(attrs, "cpv_isTracked", false);
        circleBroken = AttrValueUtil.get(attrs, "cpv_circle_broken", false);
        if (attrs.getAttr("cpv_progress_textColor").isPresent()) {
            mProgressTextColor = attrs.getAttr("cpv_progress_textColor").get().getColorValue();
        } else {
            mProgressTextColor = new Color(Color.getIntColor("#299EE3"));
        }
        mProgressTextSize = AttrValueUtil.get(attrs, "cpv_progress_textSize", AttrHelper.fp2px(NUM28, getContext()));
        mTrackWidth = AttrValueUtil.get(attrs, "cpv_track_width", AttrHelper.vp2px(NUM16, getContext()));
        mProgressWidth = AttrValueUtil.get(attrs, "cpv_progress_width", AttrHelper.vp2px(NUM16, getContext()));
        //mAnimateType = AttrValueUtil.get(attrs, "cpv_animate_type", ACCELERATE_DECELERATE_INTERPOLATOR);
        if (attrs.getAttr("cpv_track_color").isPresent()) {
            mTrackColor = attrs.getAttr("cpv_track_color").get().getColorValue();
        } else {
            mTrackColor = new Color(Color.getIntColor("#F3E5DD"));
        }

        textVisibility = AttrValueUtil.get(attrs, "cpv_progress_textVisibility", true);
        mProgressDuration = AttrValueUtil.get(attrs, "cpv_progress_duration", NUM1200);
        mScaleZoneLength = AttrValueUtil.get(attrs, "cpv_scaleZone_length", AttrHelper.vp2px(NUM22, getContext()));
        mScaleZoneWidth = AttrValueUtil.get(attrs, "cpv_scaleZone_width", AttrHelper.vp2px(NUM6, getContext()));
        mScaleZonePadding = AttrValueUtil.get(attrs, "cpv_scaleZone_padding", AttrHelper.vp2px(NUM16, getContext()));
        mScaleZoneCornerRadius = AttrValueUtil.get(attrs, "cpv_scaleZone_corner_radius",
            AttrHelper.vp2px(NUM8, getContext()));
        isGraduated = AttrValueUtil.get(attrs, "cpv_isGraduated", false);
        moveProgress = mStartProgress;
    }

    private void init() {
        progressPaint = new Paint();
        progressPaint.setAntiAlias(true);
        progressPaint.setStyle(Paint.Style.STROKE_STYLE);
        progressPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        progressPaint.setStrokeWidth(mProgressWidth);

        trackPaint = new Paint();
        trackPaint.setAntiAlias(true);
        trackPaint.setStyle(Paint.Style.STROKE_STYLE);
        trackPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        trackPaint.setStrokeWidth(mTrackWidth);

        mScaleZonePath = new Path();

        drawScaleZones(isGraduated);
        initListener();
    }

    private void initListener() {
        drawTask = new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                updateTheTrack();
                mShader = new LinearShader(new Point[]{
                    new Point(mOval.left - DEFAULT_NUM200, mOval.top - DEFAULT_NUM200),
                    new Point(mOval.right + DEFAULT_NUM20, mOval.bottom + DEFAULT_NUM20)},
                    new float[]{},
                    new Color[]{mStartColor, mEndColor},
                    Shader.TileMode.CLAMP_TILEMODE);

                /**
                 * draw the scale zone shape
                 */
                /**
                 * the shape of scale zone
                 */
                RectFloat mScaleZoneRect = new RectFloat(0, 0, mScaleZoneWidth, mScaleZoneLength);
                mScaleZonePath.addRoundRect(mScaleZoneRect, mScaleZoneCornerRadius,
                    mScaleZoneCornerRadius, Path.Direction.CLOCK_WISE);

                drawTrack(canvas);

                drawProgress(canvas);

                drawProgressText(canvas);
            }
        };
        addDrawTask(drawTask);
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
    }

    /**
     * draw the track(moving background)
     *
     * @param canvas mCanvas
     */
    private void drawTrack(Canvas canvas) {
        if (trackEnabled) {
            trackPaint.setShader(null, Paint.ShaderType.LINEAR_SHADER);
            trackPaint.setColor(mTrackColor);
            initTrack(canvas, fillEnabled);
        }
    }

    private void drawProgress(Canvas canvas) {
        progressPaint.setShader(mShader, Paint.ShaderType.LINEAR_SHADER);
        updateTheTrack();
        initProgressDrawing(canvas, fillEnabled);
    }

    private void drawScaleZones(boolean isGraduated) {
        if (isGraduated) {
            if (pathEffect == null) {
                pathEffect = new PathEffect(mScaleZonePath, mScaleZonePadding, 0, PathEffect.Style.ROTATE_STYLE);
            }
            progressPaint.setPathEffect(pathEffect);
        } else {
            pathEffect = null;
            progressPaint.setPathEffect(null);
        }
    }

    /**
     * init for track view
     *
     * @param canvas mCanvas
     * @param isFilled whether filled or not
     */
    private void initTrack(Canvas canvas, boolean isFilled) {
        if (isFilled) {
            trackPaint.setStyle(Paint.Style.FILL_STYLE);
        } else {
            trackPaint.setStyle(Paint.Style.STROKE_STYLE);
        }
        if (circleBroken) {
            canvas.drawArc(mOval, new Arc(NUM135, NUM270, isFilled), trackPaint);
        } else {
            canvas.drawArc(mOval, new Arc(NUM90, NUM360, isFilled), trackPaint);
        }
    }

    /**
     * draw the progress text
     *
     * @param canvas mCanvas
     */
    private void drawProgressText(Canvas canvas) {
        if (textVisibility) {
            Paint mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setStyle(Paint.Style.FILL_STYLE);
            mTextPaint.setTextSize(mProgressTextSize);
            mTextPaint.setColor(mProgressTextColor);
            mTextPaint.setTextAlign(LayoutAlignment.CENTER);

            String progressText = ((int) moveProgress) + "%";
            float x = (getWidth() + getPaddingLeft() - getPaddingRight()) >> 1;
            x = x + getMarginLeft() * DEFAULT_DIVISOR;
            float y = (getHeight() + getPaddingTop() - getPaddingBottom()
                - (mTextPaint.descent() + mTextPaint.ascent())) / DEFAULT_DIVISOR;
            canvas.drawText(mTextPaint, progressText, x, y);
        }
    }

    /**
     * set progress animate type
     *
     * @param type anim type
     */
    public void setAnimateType(int type) {
        //this.mAnimateType = type;
        setObjectAnimatorType(type);
    }

    /**
     * set object animation type by received
     *
     * @param animatorType object anim type
     */
    private void setObjectAnimatorType(int animatorType) {
        switch (animatorType) {
            case ACCELERATE_DECELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.ACCELERATE_DECELERATE;
                break;
            case LINEAR_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.LINEAR;
                break;
            case ACCELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                    curveType = Animator.CurveType.ACCELERATE;
                }
                break;
            case DECELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.DECELERATE;
                break;
            case OVERSHOOT_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.OVERSHOOT;
                break;
            default:
                break;
        }
    }

    /**
     * set move progress
     *
     * @param progress progress of moving
     */
    public void setProgress(float progress) {
        this.moveProgress = progress;
        refreshTheView();
    }

    public float getProgress() {
        return this.moveProgress;
    }

    /**
     * set start progress
     *
     * @param startProgress start progress
     */
    public void setStartProgress(float startProgress) {
        if (startProgress < 0 || startProgress > NUM100) {
            throw new IllegalArgumentException("Illegal progress value, please change it!");
        }
        this.mStartProgress = startProgress;
        this.moveProgress = mStartProgress;
        refreshTheView();
    }

    /**
     * set end progress
     *
     * @param endProgress end progress
     */
    public void setEndProgress(float endProgress) {
        if (endProgress < 0 || endProgress > NUM100) {
            throw new IllegalArgumentException("Illegal progress value, please change it!");
        }
        this.mEndProgress = endProgress;
        refreshTheView();
    }

    /**
     * set start color
     *
     * @param startColor start point color
     */
    public void setStartColor(Color startColor) {
        this.mStartColor = startColor;
        updateTheTrack();
        mShader = new LinearShader(new Point[]{new Point(mOval.left - DEFAULT_NUM200, mOval.top - DEFAULT_NUM200),
            new Point(mOval.right + DEFAULT_NUM20, mOval.bottom + DEFAULT_NUM20)},
            new float[]{},
            new Color[]{mStartColor, mEndColor},
            Shader.TileMode.CLAMP_TILEMODE);
        refreshTheView();
    }

    /**
     * set end color
     *
     * @param endColor end point color
     */
    public void setEndColor(Color endColor) {
        this.mEndColor = endColor;
        updateTheTrack();
        mShader = new LinearShader(new Point[]{new Point(mOval.left - DEFAULT_NUM200, mOval.top - DEFAULT_NUM200),
            new Point(mOval.right + DEFAULT_NUM20, mOval.bottom + DEFAULT_NUM20)},
            new float[]{},
            new Color[]{mStartColor, mEndColor},
            Shader.TileMode.CLAMP_TILEMODE);
        refreshTheView();
    }

    /**
     * set the width of progress stroke
     *
     * @param width stroke
     */
    public void setTrackWidth(int width) {
        mTrackWidth = AttrHelper.vp2px(width, getContext());
        trackPaint.setStrokeWidth(width);
        updateTheTrack();
        refreshTheView();
    }

    /**
     * set the width of progress stroke
     *
     * @param width stroke
     */
    public void setProgressWidth(int width) {
        mProgressWidth = AttrHelper.vp2px(width, getContext());
        progressPaint.setStrokeWidth(width);
        refreshTheView();
    }

    /**
     * set text size for inner text
     *
     * @param size text size
     */
    public void setProgressTextSize(int size) {
        mProgressTextSize = AttrHelper.fp2px(size, getContext());
        refreshTheView();
    }

    /**
     * set text color for progress text
     *
     * @param textColor
     */
    public void setProgressTextColor(Color textColor) {
        this.mProgressTextColor = textColor;
    }

    /**
     * set duration of progress moving
     *
     * @param duration
     */
    public void setProgressDuration(int duration) {
        this.mProgressDuration = duration;
    }

    /**
     * set track for progress
     *
     * @param trackAble whether track or not
     */
    public void setTrackEnabled(boolean trackAble) {
        this.trackEnabled = trackAble;
        refreshTheView();
    }

    /**
     * set track color for progress background
     *
     * @param color bg color
     */
    public void setTrackColor(Color color) {
        this.mTrackColor = color;
        refreshTheView();
    }

    /**
     * set content for progress inner space
     *
     * @param fillEnabled whether filled or not
     */
    public void setFillEnabled(boolean fillEnabled) {
        this.fillEnabled = fillEnabled;
        refreshTheView();
    }

    /**
     * set the broken circle for progress
     *
     * @param isBroken the circle broken or not
     */
    public void setCircleBroken(boolean isBroken) {
        this.circleBroken = isBroken;
        refreshTheView();
    }

    /**
     * set the scale zone type for progress view
     *
     * @param isGraduated have scale zone or not
     */
    public void setGraduatedEnabled(final boolean isGraduated) {
        this.isGraduated = isGraduated;
        new EventHandler(EventRunner.getMainEventRunner()).postTask(() -> {
            drawScaleZones(isGraduated);
        });
    }

    /**
     * set the scale zone width for it
     *
     * @param zoneWidth each zone 's width
     */
    public void setScaleZoneWidth(float zoneWidth) {
        this.mScaleZoneWidth = AttrHelper.vp2px(zoneWidth, getContext());
    }

    /**
     * set the scale zone length for it
     *
     * @param zoneLength each zone 's length
     */
    public void setScaleZoneLength(float zoneLength) {
        this.mScaleZoneLength = AttrHelper.vp2px(zoneLength, getContext());
    }

    /**
     * set each zone's distance
     *
     * @param zonePadding distance
     */
    public void setScaleZonePadding(int zonePadding) {
        this.mScaleZonePadding = AttrHelper.vp2px(zonePadding, getContext());
    }

    /**
     * set corner radius for each zone
     *
     * @param cornerRadius round rect zone's corner
     */
    public void setScaleZoneCornerRadius(int cornerRadius) {
        this.mScaleZoneCornerRadius = AttrHelper.vp2px(cornerRadius, getContext());
    }

    /**
     * set the visibility for progress inner text
     *
     * @param visibility text visible or not
     */
    public void setProgressTextVisibility(boolean visibility) {
        this.textVisibility = visibility;
    }

    /**
     * start the progress's moving
     */
    public void startProgressAnimation() {
        progressAnimator = new AnimatorValue();
        progressAnimator.setDuration(mProgressDuration);
        progressAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float progress) {
                if (mUpdateListener != null) {
                    mUpdateListener.onCircleProgressUpdate(CircleProgressView.this, progress * mEndProgress);
                }

                moveProgress = progress * mEndProgress;
                invalidate();
            }
        });

        progressAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                if (mUpdateListener != null) {
                    mUpdateListener.onCircleProgressStart(CircleProgressView.this);
                }
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (mUpdateListener != null) {
                    mUpdateListener.onCircleProgressFinished(CircleProgressView.this);
                }
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        progressAnimator.start();
    }

    /**
     * stop the progress moving
     */
    public void stopProgressAnimation() {
        if (progressAnimator != null) {
            progressAnimator.cancel();
            progressAnimator = null;
        }
    }

    /**
     * refresh the layout
     */
    private void refreshTheView() {
        invalidate();
    }

    /**
     * update the oval progress track
     */
    private void updateTheTrack() {
        if (mOval != null) {
            mOval = null;
        }
        mOval = new RectFloat(getPaddingLeft() + mTrackWidth, getPaddingTop() + mTrackWidth,
            getWidth() - getPaddingRight() - mTrackWidth - mTrackWidth / DEFAULT_DIVISOR,
            getHeight() - getPaddingBottom() - mTrackWidth - mTrackWidth / DEFAULT_DIVISOR);
    }

    /**
     * init the circle progress drawing
     *
     * @param canvas mCanvas
     * @param isFilled filled or not
     */
    private void initProgressDrawing(Canvas canvas, boolean isFilled) {
        if (isFilled) {
            progressPaint.setStyle(Paint.Style.FILL_STYLE);
        } else {
            progressPaint.setStyle(Paint.Style.STROKE_STYLE);
        }
        if (circleBroken) {
            canvas.drawArc(mOval, new Arc(NUM135 + mStartProgress * NUM2_7,
                (moveProgress - mStartProgress) * NUM2_7, isFilled), progressPaint);
        } else {
            canvas.drawArc(mOval, new Arc(NUM270 + mStartProgress * NUM3_6,
                (moveProgress - mStartProgress) * NUM3_6, isFilled), progressPaint);
        }
    }

    /**
     * 进度条更新监听
     *
     * @since 2021-04-25
     */
    public interface CircleProgressUpdateListener {
        /**
         * 进度条开始更新
         *
         * @param view
         */
        void onCircleProgressStart(Component view);

        /**
         * 进度条更新中
         *
         * @param view
         * @param progress
         */
        void onCircleProgressUpdate(Component view, float progress);

        /**
         * 进度条更新结束
         *
         * @param view
         */
        void onCircleProgressFinished(Component view);
    }

    /**
     * set the progress update listener for progress view
     *
     * @param listener update listener
     */
    public void setProgressViewUpdateListener(CircleProgressUpdateListener listener) {
        this.mUpdateListener = listener;
    }
}
